"""
Base class, type definitions, etc.
Corresponds to the metadata package from SimDB.
"""

from django.db import models
from django.conf import settings

from django.utils.encoding import smart_unicode
from django.utils import simplejson
from django.core.serializers.json import DjangoJSONEncoder

from simcat.utils import get_model_collection_sets

__all__ = ["MetadataObject", "CARDINALITY", "DATA_TYPES"]

# Apparently Django doesn't really support model-level
# validation. Save this for later, or a form.
# isValidIVOID = validators.MatchesRegularExpression("ivo://[\w\d][\w\d\-_\.!~\*'\(\)\+=]{2,}(/[\w\d\-_\.!~\*'\(\)\+=]+(/[\w\d\-_\.!~\*'\(\)\+=]+)*)?")

# TODO: sort out XML IDREF. Need to know when
# to provide and IDREF in references

class MetadataObject(models.Model):
    """
    Base class, handling the JSON serialization/deserialization
    and ID properties.

    >>> True == False
    False
    """
    # redudant, but explicit
    id = models.AutoField(primary_key=True)
    _collection_sets = []
    _container_field = None

    class Meta:
        abstract = True
        app_label = 'simcat'

    def to_json(self, indent=None):
        """Returns the object as JSON, including all contained objects."""
        return simplejson.dumps(self.to_dict(), 
                                cls=DjangoJSONEncoder,
                                ensure_ascii=False,
                                indent=indent)

    def from_json(self, json_str, preserve_ids=False):
        """
        Creates or updates an object from a JSON string.

        :arguments:
          json_str
            JSON string containing the object field and contained objects.
          preserve_ids
            Create or update the object using the primary keys from the
            JSON string. Applies to objects in collections, as well.
        """
        self.from_dict(simplejson.loads(json_str), preserve_ids)

    def from_dict(self, data, preserve_ids=False):
        """
        Creates or updates an object from a Python dict.

        :arguments:
          data
            Python dict containing the object field and contained objects.
          preserve_ids
            Create or update the object using the primary keys from the
            JSON string. Applies to objects in collections, as well.
        """
        if preserve_ids and data.has_key('id'):
            self.id = data['id']
        for field in self._meta.fields:
            if data.has_key(field.attname) and field.serialize:
                setattr(self, field.attname, data[field.attname])
        self.save()
        for m in self._meta.many_to_many:
            if data.has_key(m.attname) and m.serialize:
                for rel_id in data[m.attname]:
                    getattr(self, m.attname).add(rel_id)
        self.save()
        for collection_name in get_model_collection_sets(self.__class__):
            if data.has_key(collection_name):
                # delete all of the existing items
                getattr(self, collection_name).all().delete()
                for obj_data in data[collection_name]:
                    # remove the id to the container, in case it changed
                    if obj_data.has_key("container_field"):
                        obj_data.pop(obj_data["container_field"], None)
                    if preserve_ids:
                        obj = getattr(self, collection_name).create(id=obj_data['id'])
                    else:
                        obj = getattr(self, collection_name).create()
                    obj.from_dict(obj_data, preserve_ids)
        self.save()

    def to_dict(self):
        """Returns the object as a Python dict, including all contained objects."""
        d = {'id': self.id,
             'model':self._meta.object_name}
        # store the container field name
        if self._container_field:
            d["container_field"] = self._container_field
        # use the serialize flag to determine which fields to dump
        for field in self._meta.fields:
            if field.serialize:
                d[field.attname] = smart_unicode(getattr(self, field.attname), 
                                              strings_only=True)
        for m in self._meta.many_to_many:
            if m.serialize:
                d[m.attname] = []
                for rel_object in getattr(self, m.attname).all():
                    d[m.attname].append(rel_object.id)
        for collection_name in get_model_collection_sets(self.__class__):
            collection = []
            for obj in getattr(self, collection_name).all():
                collection.append(obj.to_dict())
            d[collection_name] = collection
        return d

    @property
    def ivoId(self):
        return '%s#%s/%d' % (settings.SIMDB_IVO_ROOT, self._meta.object_name, self.id)

    @property
    def xmlId(self):
        t = '%s%d' % (self._meta.object_name, self.id)
        t = t.replace(':', '')
        return t

# selections for various fields

CARDINALITY = (("0..1", "Corresponds to 'Optional element'"), 
               ("1", "Corresponds to 'Mandatory element'"), 
               ("0..*", "Corresponds to 'Multiple but maybe Optional element'"), 
               ("1..*", "Corresponds to 'Multiple with at least one element'"))

DATA_TYPES = (("boolean", "A standard boolean type, with values 'true' and 'false', possibly represented differently in physical representations of the model."), 
              ("complex", "A standard complex type. Here no limits on accuracy of constituent Re and Im parts."), 
              ("datetime", "Represents an exact time on an exact, given date."), 
              ("double", "A real*8 floating point datatype."), 
              ("float", "A real*4 floating point datatype."), 
              ("int", "An integer*4 integer datatype."), 
              ("long", "An integer*8 integer datatype."), 
              ("rational", "A rational datatype, consisting of two integers representing the numerator and denominator."), 
              ("short", "an integer*2 integer datatype."), 
              ("string", "A standard string dattaype consisting of any type of character."))

